﻿using System;
using SpiceNet.Circuits;
using SpiceNet.Diagnostics;
using SpiceNet.Simulations;
using System.Numerics;
using SpiceNet.Parameters;

namespace SpiceNet.Models
{
    /// <summary>
    /// An abstract definition of SPICE devices (an instance)
    /// </summary>
    public abstract class Instance : Parameterized
    {
        /// <summary>
        /// Number of terminals on this device
        /// </summary>
        public string[] Terminals { get; protected set; }

        /// <summary>
        /// The type of model that this instance can use
        /// </summary>
        public Type ModelType { get; private set; }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name"></param>
        protected Instance(string name, int terminals, Type modeltype) : base(name ?? "")
        {
            ModelType = modeltype;
            Terminals = new string[terminals];
        }

        /// <summary>
        /// Connect the device to nodes
        /// </summary>
        /// <param name="nodes"></param>
        public void Connect(params string[] nodes)
        {
            // Check number of terminals
            if (nodes.Length != Terminals.Length)
                throw new CircuitException(string.Format("{0}: Invalid amount of nodes. {1} given, {2} expected", Name, nodes?.Length ?? 0, Terminals?.Length ?? 0));
            for (int i = 0; i < Terminals.Length; i++)
            {
                if (String.IsNullOrEmpty(nodes[i]))
                    throw new ArgumentNullException("node " + (i + 1));
                Terminals[i] = nodes[i];
            }
        }

        /// <summary>
        /// Bind the nodes, also allocating
        /// </summary>
        /// <param name="nodes"></param>
        protected virtual CircuitNode[] BindNodes(Circuit ckt, params NodeType[] extra)
        {
            // Map connected nodes
            CircuitNode[] nodes = new CircuitNode[Terminals.Length + extra.Length];
            for (int i = 0; i < Terminals.Length; i++)
                nodes[i] = ckt.Nodes.Map(Terminals[i]);

            // Map equations that cannot be connected to, but need to be present
            for (int i = 0; i < extra.Length; i++)
                nodes[i + Terminals.Length] = ckt.Nodes.Map(null, extra[i]);

            // Return the list of nodes
            return nodes;
        }

        /// <summary>
        /// Setup the instance
        /// </summary>
        /// <param name="ckt">The parent circuit</param>
        /// <param name="model">The parent model</param>
        /// <param name="p">Optional parameters passed down by the model</param>
        public abstract void Setup(Circuit ckt, Model model, object p = null);

        /// <summary>
        /// Clean up whatever allocated memory
        /// Since C# cleans up automatically, most instances don't need this method
        /// </summary>
        /// <param name="ckt"></param>
        /// <param name="model"></param>
        /// <param name="p"></param>
        public virtual void Unsetup(Circuit ckt, Model model, object p = null)
        {
            // Do nothing
            return;
        }

        /// <summary>
        /// Set up the instance for Pole-Zero analysis
        /// </summary>
        /// <param name="ckt">The parent circuit</param>
        /// <param name="model">The parent model</param>
        /// <param name="p">Optional parameters passed down by the model</param>
        public virtual void PzSetup(Circuit ckt, Model model, object p = null)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Set up the instance for Sensitivity analysis
        /// </summary>
        /// <param name="ckt">The parent circuit</param>
        /// <param name="model">The parent model</param>
        /// <param name="p">Optional parameters passed down by the model</param>
        public virtual void SenSetup(Circuit ckt, Model model, object p = null)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Perform temperature-dependent calculations
        /// </summary>
        /// <param name="ckt">The parent circuit</param>
        /// <param name="model">The parent model</param>
        /// <param name="p">Optional parameters passed down by the model</param>
        public abstract void Temperature(Circuit ckt, Model model, object p = null);

        /// <summary>
        /// Load the device for large-signal analysis
        /// This method should also calculate any small-signal signal parameters if the circuit calls for it
        /// </summary>
        /// <param name="ckt"></param>
        /// <param name="model"></param>
        /// <param name="p"></param>
        public abstract void Load(Circuit ckt, Model model, object p = null);

        /// <summary>
        /// Load the device for small-signal analysis
        /// Note by the author: The frequency is given as a complex variable. In most cases, this is the exact 
        /// same function as PzLoad if you allow for any complex variable. This can save a lot of work.
        /// </summary>
        /// <param name="ckt"></param>
        /// <param name="model"></param>
        /// <param name="p"></param>
        public virtual void AcLoad(Circuit ckt, Model model, object p = null)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Load the device for pole-zero analysis
        /// Note by the author: The frequency is given as a complex variable. In most cases, this is the exact 
        /// same function as PzLoad if you allow for any complex variable. This can save a lot of work.
        /// The AcLoad() function if not overridden.
        /// </summary>
        /// <param name="ckt">The parent circuit</param>
        /// <param name="model">The parent model</param>
        /// <param name="p">Optional parameters passed down by the model</param>
        public virtual void PzLoad(Circuit ckt, Model model, object p = null)
        {
            // Call the AcLoad() function as a default
            AcLoad(ckt, model, p);
        }

        /// <summary>
        /// Load the device for sensitivity analysis
        /// </summary>
        /// <param name="ckt">The parent circuit</param>
        /// <param name="model">The parent model</param>
        /// <param name="p">Optional parameters passed down by the model</param>
        public virtual void SenLoad(Circuit ckt, Model model, object p = null)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Update the device sensitivity info
        /// </summary>
        /// <param name="ckt">The parent circuit</param>
        /// <param name="model">The parent model</param>
        /// <param name="p">Optional parameters passed down by the model</param>
        public virtual void SenUpdate(Circuit ckt, Model model, object p = null)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Load the device AC sensitivity info
        /// </summary>
        /// <param name="ckt">The parent circuit</param>
        /// <param name="model">The parent model</param>
        /// <param name="p">Optional parameters passed down by the model</param>
        public virtual void SenAcLoad(Circuit ckt, Model model, object p = null)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Perform distortion analysis with this device
        /// </summary>
        /// <param name="ckt">The parent circuit</param>
        /// <param name="model">The parent model</param>
        /// <param name="p">Optional parameters passed down by the model</param>
        public virtual void Distortion(Circuit ckt, Model model, object p = null)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Load the device for noise analysis
        /// </summary>
        /// <param name="ckt">The parent circuit</param>
        /// <param name="mode">The current output mode of the noise - density or integrated</param>
        /// <param name="operation">The current operation - opening, calculating or closing</param>
        /// <param name="data">The current noise data</param>
        /// <param name="OnDens">The current total density</param>
        /// <param name="model">The parent model</param>
        /// <param name="p">Optional parameters passed down by the model</param>
        public virtual void NoiseLoad(Circuit ckt,
            Noise.OutputMode mode,
            Noise.Operation operation,
            NoiseData data,
            ref double OnDens,
            Model model,
            object p = null)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Check if the device can accept the current solution at the current timepoint
        /// If not, it should throw an exception
        /// </summary>
        /// <param name="ckt">The parent circuit</param>
        /// <param name="model">The parent model</param>
        /// <param name="p">Optional parameters passed down by the model</param>
        public virtual void Accept(Circuit ckt, Model model, object p = null)
        {
            return;
        }

        /// <summary>
        /// Set the initial conditions for the device
        /// </summary>
        /// <param name="ckt">The parent circuit</param>
        /// <param name="model">The parent model</param>
        /// <param name="p">Optional parameters passed down by the model</param>
        public virtual void SetIC(Circuit ckt, Model model, object p = null)
        {
            // Do nothing
            return;
        }

        /// <summary>
        /// Reserve a state in the state vector during setup
        /// </summary>
        /// <param name="ckt">The parent circuit</param>
        /// <param name="model">The parent model</param>
        /// <param name="p">Optional parameters passed down by the model</param>
        /// <returns>The starting index of the created states</returns>
        protected int CreateState(Circuit ckt, int amount = 1)
        {
            // Store the current index
            int index = ckt.NumStates;

            // Notify that we are using an extra state
            ckt.NumStates += amount;

            // Return the index that was reserved
            return index;
        }

        /// <summary>
        /// Limit the per-iteration change of PN junction voltages
        /// Defined as DEVpnjlim in devsup.c
        /// </summary>
        /// <param name="vnew"></param>
        /// <param name="vold"></param>
        /// <param name="vt"></param>
        /// <param name="vcrit"></param>
        /// <returns></returns>
        protected static double pnjlim(double vnew, double vold, double vt, double vcrit, ref bool limited)
        {
            double arg;
            if ((vnew > vcrit) && (Math.Abs(vnew - vold) > (vt + vt)))
            {
                if (vold > 0)
                {
                    arg = 1 + (vnew - vold) / vt;
                    if (arg > 0)
                        vnew = vold + vt * Math.Log(arg);
                    else
                        vnew = vcrit;
                }
                else
                    vnew = vt * Math.Log(vnew / vt);
                limited = true;
            }
            else
                limited = false;
            return vnew;
        }

        /// <summary>
        /// Limit fet
        /// Defined as DEVfetlim in devsup.c
        /// </summary>
        /// <param name="vn"></param>
        /// <param name="vo"></param>
        /// <param name="vto"></param>
        /// <returns></returns>
        protected static double fetlim(double vnew, double vold, double vto)
        {
            double vtsthi, vtstlo, vtox, delv, vtemp;

            vtsthi = Math.Abs(2 * (vold - vto)) + 2;
            vtstlo = vtsthi / 2 + 2.0;
            vtox = vto + 3.5;
            delv = vnew - vold;

            if (vold >= vto)
            {
                if (vold >= vtox)
                {
                    if (delv <= 0)
                    {
                        /* going off */
                        if (vnew >= vtox)
                        {
                            if (-delv > vtstlo)
                                vnew = vold - vtstlo;
                        }
                        else
                            vnew = Math.Max(vnew, vto + 2);
                    }
                    else
                    {
                        /* staying on */
                        if (delv >= vtsthi)
                            vnew = vold + vtsthi;
                    }
                }
                else
                {
                    /* middle region */
                    if (delv <= 0)
                        /* decreasing */
                        vnew = Math.Max(vnew, vto - 0.5);
                    else
                        /* increasing */
                        vnew = Math.Min(vnew, vto + 4.0);
                }
            }
            else
            {
                /* off */
                if (delv <= 0)
                {
                    if (-delv > vtsthi)
                        vnew = vold - vtsthi;
                }
                else
                {
                    vtemp = vto + 0.5;
                    if (vnew <= vtemp)
                    {
                        if (delv > vtstlo)
                            vnew = vold + vtstlo;
                    }
                    else
                        vnew = vtemp;
                }
            }
            return (vnew);
        }

        /// <summary>
        /// Limit vds
        /// Defined as DEVlimvds in devsup.c file
        /// </summary>
        /// <param name="vnew"></param>
        /// <param name="?"></param>
        /// <returns></returns>
        protected static double limvds(double vnew, double vold)
        {

            if (vold >= 3.5)
            {
                if (vnew > vold)
                {
                    vnew = Math.Min(vnew, (3 * vold) + 2);
                }
                else
                {
                    if (vnew < 3.5)
                    {
                        vnew = Math.Max(vnew, 2);
                    }
                }
            }
            else
            {
                if (vnew > vold)
                {
                    vnew = Math.Min(vnew, 4);
                }
                else
                {
                    vnew = Math.Max(vnew, -0.5);
                }
            }
            return (vnew);
        }

        /// <summary>
        /// Calculate a noise source
        /// Defined as NevalSrc in nevalsrc.c
        /// </summary>
        /// <param name="ckt"></param>
        /// <param name="noise"></param>
        /// <param name="lnNoise"></param>
        /// <param name="type"></param>
        /// <param name="node1"></param>
        /// <param name="node2"></param>
        /// <param name="param"></param>
        protected void NevalSrc(Circuit ckt, 
            ref double noise, 
            ref double lnNoise,
            Noise.Sources type,
            int node1,
            int node2,
            double param)
        {
            CircuitStateComplex state = ckt.StateComplex;
            Complex Val;
            double gain;

            Val = state.Solution[node1] - state.Solution[node2]; // state.Rhs[node1] - state.Rhs[node2];
            gain = (Val.Real * Val.Real) + (Val.Imaginary * Val.Imaginary);

            switch (type)
            {
                case Simulations.Noise.Sources.SHOTNOISE:
                    noise = gain * 2.0 * Circuit.CHARGE * Math.Abs(param); // Param is the DC current in a semiconductor
                    lnNoise = Math.Log(Math.Max(noise, Simulations.Noise.N_MINLOG));
                    break;

                case Simulations.Noise.Sources.THERMNOISE:
                    noise = gain * 4.0 * Circuit.CONSTBoltz * ckt.Config.Temperature * param; // Parma is the conductance of a resistor
                    lnNoise = Math.Log(Math.Max(noise, Simulations.Noise.N_MINLOG));
                    break;

                case Simulations.Noise.Sources.N_GAIN:
                    noise = gain;
                    break;
            }
        }
    }
}
